﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Media;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using RoboRallyAPI;
using SimpleAI;
using Timer = System.Windows.Forms.Timer;

namespace RoboRally
{
	internal class Engine
	{
		/// <summary>
		/// The game mode.
		/// </summary>
		internal enum MODE
		{
			/// <summary>
			/// The game has been ended.
			/// </summary>
			STOP,

			/// <summary>
			/// The game is playing.
			/// </summary>
			PLAY,

			/// <summary>
			/// The game is paused.
			/// </summary>
			PAUSE
		};

		/// <summary>
		/// The turn phase.
		/// </summary>
		private enum PHASE
		{
			/// <summary>
			/// Move robots.
			/// </summary>
			MOVE_ROBOT,

			/// <summary>
			/// Board movement.
			/// </summary>
			MOVE_BOARD,

			/// <summary>
			/// Fire lasers.
			/// </summary>
			FIRE,

			/// <summary>
			/// Check for robots on flag and archive spots.
			/// </summary>
			FLAG_ARCHIVE
		}

		// how many milliseconds allowed for each turn.
		private const int TURN_TIME_LIMIT = 1000; // playoff uses 1000
		// how many times we update sprites per second
		private const int FRAMES_PER_SECOND = 100; // playoff uses 24
		// how many of the above frames before we move a unit/do next turn.
		private const int FRAMES_PER_MOVE = 0;	// playoff uses 6

		// the [card] to do on the next tick. Value is 0..4
		private int cardOn;
		// the phase on
		private PHASE phaseOn = PHASE.MOVE_ROBOT;
		// the turn on
		private int turnOn;

		// the number of frame ticks from the start of the game.
		private int frameTicks;
		// the number of move ticks from the start of the game.
		private int moveTicks;
		// the number of frame ticks to occur until the next move tick.
		private int ticksTillMove;

		// The game map.
		internal GameMapInternal MainMap { get; private set; }

		// the engines of each AI player.
		private IPlayer[] playerEngines;
		// all players
		private readonly List<PlayerInternal> players = new List<PlayerInternal>();

		// the explosion sprites.
		internal readonly List<Sprite> sprites = new List<Sprite>();
		// the laser beams firing.
		internal readonly List<LaserBeam> laserBeams = new List<LaserBeam>();

		// the main window.
		private readonly IUserDisplay mainWindow;

		// The game timer. Fires on every animation tick.
		private Timer timerWorker;

		// plays the explosion.
		private readonly SoundPlayer explosionPlayer;

		// anytime we need a random number.
		private static readonly Random rand = new Random();

		// names for SimpleAI
		private static readonly string[] simpleAiNames = {
		                                          	"Romy White", "Michele Weinberger", "Lloyd Christmas", "Harry Dunne",
		                                          	"Forrest Gump", "Paris Hilton", "George Bush", "Sarah Palin"
		                                          };

		/// <summary>
		/// Create the engine.
		/// </summary>
		/// <param name="mainWindow">The main window.</param>
		public Engine(IUserDisplay mainWindow)
		{
			this.mainWindow = mainWindow;
			// create the map
			MainMap = GameMapInternal.CreateMap();
			using (
				Stream wavFile = Assembly.GetExecutingAssembly().GetManifestResourceStream("RoboRally.Resources.Explosion.wav"))
			{
				explosionPlayer = new SoundPlayer(wavFile);
				explosionPlayer.Load();
			}

			// load the AIs
			LoadAiPlayers(true);
		}

		private void LoadAiPlayers(bool showErrors)
		{

			List<IPlayer> playerDLLs = new List<IPlayer>();
			System.Collections.Specialized.NameValueCollection coll =
				(System.Collections.Specialized.NameValueCollection)
				System.Configuration.ConfigurationManager.GetSection("RoboRally");
			for (int ind = 0; ind < 8; ind++)
			{
				string key = "player" + (ind + 1);
				if (!coll.AllKeys.Contains(key))
				{
					playerDLLs.Add(new SimplePlayer(simpleAiNames[ind]));
					continue;
				}

				String dllFilename = key;
				bool gotIt = false;
				try
				{
					dllFilename = Path.GetFullPath(coll[key]);
					if (dllFilename.EndsWith(".py"))
					{

						var ipEngine = new IronPythonEngine();
						IPlayer aiPlayer = ipEngine.Execute(dllFilename);
						playerDLLs.Add(aiPlayer);
						gotIt = true;
						Trace.WriteLine(string.Format("Loaded IronPython player from {0}", dllFilename));
					}
					else
					{
						Assembly assembly = Assembly.LoadFile(dllFilename);
						foreach (Type typeOn in assembly.GetTypes())
						{
							if (typeOn.GetInterface(typeof (IPlayer).FullName) != null)
							{
								IPlayer aiPlayer = (IPlayer) Activator.CreateInstance(typeOn);
								playerDLLs.Add(aiPlayer);
								gotIt = true;
								Trace.WriteLine(string.Format("loaded DLL {0} using class {1}", assembly.FullName, typeOn.FullName));
								break;
							}
						}
					}
				}
				catch (Exception ex)
				{
					Trace.WriteLine(string.Format("Could not load DLL {0}, error: {1}, stack: {2}", dllFilename, ex.Message,
					                              ex.StackTrace));
					if (showErrors)
						MessageBox.Show(string.Format("Could not load AI {0}\nError: {1}", dllFilename, ex.Message), "RoboRally");
				}
				if (!gotIt)
					playerDLLs.Add(new SimplePlayer(simpleAiNames[ind]));
			}

			// random order & assign
			playerEngines = playerDLLs.ToArray(); //.OrderBy(pl => rand.Next()).ToArray();
		}

		/// <summary>
		/// The game play mode.
		/// </summary>
		public MODE Mode
		{
			get
			{
				if (timerWorker != null && timerWorker.Enabled)
					return MODE.PLAY;
				if (timerWorker != null)
					return MODE.PAUSE;
				return MODE.STOP;
			}
		}

		/// <summary>
		/// All of the players.
		/// </summary>
		internal List<PlayerInternal> Players
		{
			get { return players; }
		}

		/// <summary>
		/// Start or continue (from pause) the game.
		/// </summary>
		public void Play()
		{
			if (timerWorker == null)
			{
				// in case a reset
				frameTicks = moveTicks = ticksTillMove = 0;
				cardOn = 0;
				turnOn = 0;
				phaseOn = PHASE.MOVE_ROBOT;

				players.Clear();
				sprites.Clear();
				MainMap = GameMapInternal.CreateMap();

				LoadAiPlayers(false);
				SetupPlayers();

				mainWindow.SetupGame();
				ValidateData();

				timerWorker = new Timer {Interval = 1000/FRAMES_PER_SECOND};
				timerWorker.Tick += FrameTick;
			}
			timerWorker.Start();
		}

		/// <summary>
		/// Pause the game.
		/// </summary>
		public void Pause()
		{
			timerWorker.Stop();
		}

		/// <summary>
		/// End the game.
		/// </summary>
		public void Stop()
		{
			if (timerWorker != null)
			{
				timerWorker.Stop();
				timerWorker.Dispose();
				timerWorker = null;
			}

			// clear off explosions
			sprites.Clear();
			laserBeams.Clear();
			mainWindow.UpdateMap();
		}

		/// <summary>
		/// The main timer tick handler. Calls all game logic from here on each tick.
		/// </summary>
		private void FrameTick(object sender, EventArgs e)
		{

			// update sprites. Some may be deleted. Update if any sprites
			bool updateSprites = sprites.Count > 0;
			List<Sprite> dead = sprites.Where(spriteOn => spriteOn.IncreaseTick()).ToList();
			foreach (Sprite spriteOn in dead)
				sprites.Remove(spriteOn);

			// if not time for a move, we're done. Update if any sprites changed.
			frameTicks++;
			ticksTillMove--;
			if (ticksTillMove > 0)
			{
				if (updateSprites)
					mainWindow.UpdateMap();
				return;
			}

			// check for winner
			List<PlayerInternal> winningPlayers = players.Where(pl => pl.HasWon).ToList();
			if (winningPlayers.Count > 0)
			{
				Stop();
				if (winningPlayers.Count == 1)
				{
					mainWindow.StatusMessage(string.Format("Player {0} has won! {1} player(s) with 2 flags, {2} player(s) with 1 flag, {3} dead player(s)",
									  winningPlayers[0].PlayerEngine.Name, 
									  players.Count(pl => (pl.Mode != Player.MODE.DEAD) && (pl.FlagsTouched == 2)), 
									  players.Count(pl => (pl.Mode != Player.MODE.DEAD) && (pl.FlagsTouched == 1)),
									  players.Count(pl => pl.Mode == Player.MODE.DEAD)));
				}
				else
				{
					StringBuilder buf = new StringBuilder("The winning players are: ");
					foreach (PlayerInternal plyrOn in winningPlayers)
						buf.Append(string.Format("{0}, ", plyrOn.PlayerEngine.Name));
					buf.Remove(buf.Length - 2, 2);
					mainWindow.StatusMessage(buf.ToString());
				}
				mainWindow.UpdatePlayers();
				mainWindow.UpdateMenu();
				ValidateData();
				return;
			}

			// check for everyone dead
			int numPlayers = players.Count(pl => pl.Mode != Player.MODE.DEAD);
			if (numPlayers == 0)
			{
				Stop();
				mainWindow.StatusMessage("All players are dead - no winner!");
				mainWindow.UpdatePlayers();
				mainWindow.UpdateMenu();
				ValidateData();
				return;
			}

			// ok, time to execute this phase.
			switch (phaseOn)
			{
				case PHASE.MOVE_ROBOT:
					// if phase 0, start the turn.
					if (cardOn == 0)
					{
						turnOn++;
						ExecuteStartTurn();
						mainWindow.UpdatePlayers();
					}

					mainWindow.TurnNumber(turnOn, cardOn + 1);
					ExecuteMoveRobot();
					phaseOn = PHASE.MOVE_BOARD;
					break;

				case PHASE.MOVE_BOARD:
					ExecuteMoveBoard();
					phaseOn = PHASE.FIRE;
					break;

				case PHASE.FIRE:
					ExecuteFire();
					phaseOn = PHASE.FLAG_ARCHIVE;
					mainWindow.UpdatePlayers();
					break;

				case PHASE.FLAG_ARCHIVE:
					ExecuteFlagAndArchive();
					laserBeams.Clear();
					cardOn++;

					// if last phase, end the turn.
					if (cardOn >= 5)
						ExecuteEndTurn();
					phaseOn = PHASE.MOVE_ROBOT;
					mainWindow.UpdatePlayers();
					break;
			}

			moveTicks++;
			ticksTillMove = FRAMES_PER_MOVE;
			mainWindow.UpdateMap();
		}

		#region execute turn/phase

		// get the cards for all players
		private void ExecuteStartTurn()
		{

#if DEBUG
			Trace.WriteLine(string.Format("starting turn {0}", turnOn));
			foreach (PlayerInternal player in players)
				Trace.WriteLine(string.Format("     Player {0}, mode {1}, Lives {2}, Damage {3}, Mode {4}, Location {5}",
				                              player.PlayerEngine.Name, player.Mode, player.Lives, player.Damage, player.Mode,
				                              player.Robot.Location));
#endif

			// create 40 cards.
			// unique priorities across all
			// ROBOT_MOVE num of cards in enum order are 6, 18, 12, 6, 18, 18, 6
			List<int> priorityNums = CreatePriorityNumbers();
			List<Card> cards = new List<Card>();
			cards.AddRange(priorityNums.Take(6).Select(priority => new Card(Card.ROBOT_MOVE.BACKWARD_ONE, priority)));
			cards.AddRange(priorityNums.Skip(6).Take(18).Select(priority => new Card(Card.ROBOT_MOVE.FORWARD_ONE, priority)));
			cards.AddRange(priorityNums.Skip(6 + 18).Take(12).Select(priority => new Card(Card.ROBOT_MOVE.FORWARD_TWO, priority)));
			cards.AddRange(priorityNums.Skip(6 + 18 + 12).Take(6).Select(priority => new Card(Card.ROBOT_MOVE.FORWARD_THREE, priority)));
			cards.AddRange(priorityNums.Skip(6 + 18 + 12 + 6).Take(18).Select(priority => new Card(Card.ROBOT_MOVE.ROTATE_LEFT, priority)));
			cards.AddRange(priorityNums.Skip(6 + 18 + 12 + 6 + 18).Take(18).Select(priority => new Card(Card.ROBOT_MOVE.ROTATE_RIGHT, priority)));
			cards.AddRange(priorityNums.Skip(6 + 18 + 12 + 6 + 18 + 18).Take(6).Select(priority => new Card(Card.ROBOT_MOVE.ROTATE_UTURN, priority)));

			Trace.WriteLine("Turns:");
			foreach (PlayerInternal player in players)
			{
				ValidateData();
				if (player.Mode == Player.MODE.DEAD)
				{
					player.Cards = new Card[0];
					continue;
				}
				// if powered down keep old cards
				if (player.Mode == Player.MODE.POWER_DOWN)
					continue;

				// make safe copies of all objects to pass down.
				List<Player> apiAllPlayers = players.Select(pl => new Player(pl)).ToList();
				Player apiPlayerTurn = apiAllPlayers.Where(pl => pl.Guid == player.Guid).First();

				// grab the players dealt cards
				int numCards = 9 - player.Damage;
				List<Card> dealtCards = new List<Card>();
				while (dealtCards.Count < numCards)
				{
					int index = rand.Next(cards.Count - 1);
					Trap.trap(index == cards.Count - 1);
					dealtCards.Add(cards[index]);
					cards.RemoveAt(index);
				}

				// get the turn - PlayerTurn turn = CallPlayer(map, apiPlayerTurn, apiAllPlayers, playerCards);
				PlayerTurn turn = null;
				Exception turnException = null;
				// This is sloppy as we leave this task to just run if it times out. However, there is no abort and a timeout is very unlikely
				// so this is the best of all the bad solutions.
				Task task = new Task(() =>
				                     	{
				                     		try
				                     		{
												turn = player.PlayerEngine.Turn(new GameMap(MainMap), apiPlayerTurn, apiAllPlayers.ToArray(), dealtCards.ToArray());
				                     		}
				                     		catch (Exception ex)
				                     		{
				                     			turnException = ex;
				                     		}
				                     	});
				task.Start();
				bool completed = task.Wait(TURN_TIME_LIMIT);
				if (completed)
					task.Dispose();
				if (turnException != null)
					mainWindow.StatusMessage(string.Format("{0} Turn threw exception {1}", player.PlayerEngine.Name,
					                                       turnException.Message));
				else if (!completed)
					mainWindow.StatusMessage(string.Format("{0} Turn timed out", player.PlayerEngine.Name));

				// turn this into a validated set of cards. Add random if too few, set with priority, copy over locked.
				List<Card> turnCards = new List<Card>();
				if (completed && (turnException == null) && (turn != null))
					turnCards.AddRange(turn.Cards);
				List<Card> playerCards = new List<Card>();
				int numLockedCards = player.NumLockedCards;

				// make sure they selected from the list we gave them
				for (int ind = 0; ind < 5 - numLockedCards; ind++)
				{
					Card moveOn = new Card(); // always set but the compiler doesn't know that.
					bool isValid = false;
					if (ind < turnCards.Count)
					{
						if (dealtCards.Contains(turnCards[ind]))
						{
							moveOn = turnCards[ind];
							dealtCards.Remove(moveOn);
							isValid = true;
						}
					}
					if (! isValid)
					{
						moveOn = dealtCards[rand.Next(dealtCards.Count - 1)];
						dealtCards.Remove(moveOn);
					}
					playerCards.Add(moveOn);
				}

				for (int ind = 5 - numLockedCards; ind < 5; ind++)
					if (player.Cards.Length > ind)
						playerCards.Add(player.Cards[ind]);
					else Trap.trap(); // should never happen
				player.Cards = playerCards.ToArray();

				if ((turn != null) && (turn.IsPowerDown))
					player.AnnouncePowerMode();

				Trace.WriteLine(string.Format("     turn - Player: {0}; Cards: {1}, {2}, {3}, {4}, {5}{6}", player.PlayerEngine.Name,
				                              playerCards[0], playerCards[1], playerCards[2], playerCards[3], playerCards[4],
				                              player.PowerMode != Player.POWER_MODE.ANNOUNCED ? "" : " - power down announced"));
				ValidateData();
			}
		}

		// 84 unique numbers for 10 to 990
		private List<int> CreatePriorityNumbers()
		{

			// get priorities for locked cards
			List<int> arrLocked = new List<int>();
			foreach (PlayerInternal playerOn in players)
			{
				int numLockedCards = Math.Min(0, Math.Max(playerOn.Damage - 4, 0));
				for (int ind = 5 - numLockedCards; ind < playerOn.Cards.Length; ind++)
					arrLocked.Add(playerOn.Cards[ind].Priority);
			}

			List<int> arr = new List<int>();
			while (arr.Count < 84)
			{
				int num = rand.Next(1, 99)*10;
				if ((!arr.Contains(num)) && (!arrLocked.Contains(num)))
					arr.Add(num);
			}
			return arr;
		}

		// move the robots for 1 phase (card).
		private void ExecuteMoveRobot()
		{


			Trace.WriteLine(string.Format("Move card[{0}]", cardOn));
			List<PlayerInternal> plyrMove =
				players.Where(pl => pl.IsVisible && (pl.PowerMode != Player.POWER_MODE.DOWN) && (pl.Cards.Length > cardOn)).OrderBy(
					pl => pl.Cards[cardOn].Priority).Reverse().ToList();

			foreach (PlayerInternal playerOn in plyrMove)
			{
				ValidateData();
				// a previous player may have killed this player by pushing it off the board
				if ((!playerOn.IsVisible) || (playerOn.Cards.Length <= cardOn))
					continue;

				Card card = playerOn.Cards[cardOn];
				Trace.WriteLine(string.Format("     move requested - Player: {0}; Card: {1}, from {2}", playerOn.PlayerEngine.Name,
				                              card, playerOn.Robot.Location));
				int steps = 0;
				switch (card.Move)
				{
					case Card.ROBOT_MOVE.BACKWARD_ONE:
						steps = -1;
						break;
					case Card.ROBOT_MOVE.FORWARD_ONE:
						steps = 1;
						break;
					case Card.ROBOT_MOVE.FORWARD_TWO:
						steps = 2;
						break;
					case Card.ROBOT_MOVE.FORWARD_THREE:
						steps = 3;
						break;
					case Card.ROBOT_MOVE.ROTATE_LEFT:
						playerOn.Robot.Location = playerOn.Robot.Location.Rotate(-1);
						break;
					case Card.ROBOT_MOVE.ROTATE_RIGHT:
						playerOn.Robot.Location = playerOn.Robot.Location.Rotate(1);
						break;
					case Card.ROBOT_MOVE.ROTATE_UTURN:
						playerOn.Robot.Location = playerOn.Robot.Location.Rotate(2);
						break;
				}

				if (steps == 0)
				{
					Trace.WriteLine(string.Format("     move complete - Player: {0}; Card: {1}, now {2}", playerOn.PlayerEngine.Name,
					                              card, playerOn.Robot.Location));
					ValidateData();
					continue;
				}

				// move pushing other robots if needed.
				BoardLocation start = playerOn.Robot.Location;
				MapSquare.DIRECTION dir = steps >= 0
				                          	? playerOn.Robot.Location.Direction
				                          	: playerOn.Robot.Location.Rotate(2).Direction;
				while (steps != 0)
				{
					if (MoveRobot(playerOn, dir) != MOVE_RESULT.OK)
						break;

					steps = steps >= 0 ? steps - 1 : steps + 1;
				}
				Trace.WriteLine(string.Format("     move complete - Player: {0}; Card: {1}, from {2} to {3}",
				                              playerOn.PlayerEngine.Name, card, start, playerOn.Robot.Location));
				ValidateData();
			}
		}

		private enum MOVE_RESULT
		{
			OK,
			DEAD,
			STUCK
		}

		private MOVE_RESULT MoveRobot(PlayerInternal playerOn, MapSquare.DIRECTION dir)
		{

			Utilities.MovePoint mp = Utilities.Move(MainMap, playerOn.Robot.Location.MapPosition, dir);
			// if dead, handle that
			if (mp.Dead)
			{
				playerOn.DecreaseLives();
				if (playerOn.Mode == Player.MODE.DEAD)
				{
					Trace.WriteLine(string.Format("Player {0} has been killed", playerOn.PlayerEngine.Name));
					mainWindow.StatusMessage(string.Format("Player {0} has been killed", playerOn.PlayerEngine.Name));
				}
				else
					Trace.WriteLine(string.Format("destroyed - Player {0}, Lives {1}", playerOn.PlayerEngine.Name, playerOn.Lives));
				return MOVE_RESULT.DEAD;
			}
			// if can't move we're done
			if (mp.Location.MapPosition == playerOn.Robot.Location.MapPosition)
				return MOVE_RESULT.STUCK;
			// if hitting a robot, can we move it?
			PlayerInternal plyrMoveRobot =
				players.Where(pl => (pl.IsVisible) && (pl.Robot.Location.MapPosition == mp.Location.MapPosition)).FirstOrDefault();
			if (plyrMoveRobot != null)
			{
				Trace.WriteLine(string.Format("          push - Player: {0} at {1} trying {2} at {3}", playerOn.PlayerEngine.Name,
				                              playerOn.Robot.Location, plyrMoveRobot.PlayerEngine.Name, plyrMoveRobot.Robot.Location));
				if (MoveRobot(plyrMoveRobot, dir) == MOVE_RESULT.STUCK)
				{
					Trace.WriteLine(string.Format("          push failed - Player: {0} at {1} blocked by {2} at {3}",
					                              playerOn.PlayerEngine.Name, playerOn.Robot.Location, plyrMoveRobot.PlayerEngine.Name,
					                              plyrMoveRobot.Robot.Location));
					ValidateData();
					return MOVE_RESULT.STUCK;
				}
				Trace.WriteLine(string.Format("          push successful - Player: {0} at {1} moved {2} at {3}",
				                              playerOn.PlayerEngine.Name, playerOn.Robot.Location, plyrMoveRobot.PlayerEngine.Name,
				                              plyrMoveRobot.Robot.Location));
				ValidateData();
			}
			playerOn.Robot.Location = new BoardLocation(mp.Location.MapPosition, playerOn.Robot.Location.Direction);
			return MOVE_RESULT.OK;
		}

		// board (conveyor and gear) movement.
		private void ExecuteMoveBoard()
		{

			Trace.WriteLine("Conveyor and gear moves:");
			// We move if the dest is a conveyor or it's unoccupied. 
			// bugbug - The unoccupied test works for the one map we have - the one conveyor exit square has a wall after it.
			List<PlayerInternal> plyrMoveBelt = players.Where(playerInternal => playerInternal.IsVisible).ToList();
			for (int level = 2; level > 0; level--)
				foreach (PlayerInternal plyrOn in plyrMoveBelt)
				{
					// needs to be on a belt - and we do the 2 move, then all
					MapSquare sq = MainMap.GetSquare(plyrOn.Robot.Location.MapPosition);
					if ((sq.Conveyor == null) || (sq.Conveyor.Speed < level))
						continue;

					Utilities.MovePoint mp = Utilities.Move(MainMap, plyrOn.Robot.Location.MapPosition, sq.Conveyor.Direction);
					MapSquare sqDest = MainMap.GetSquare(mp.Location.MapPosition);
					if (sqDest.Type == MapSquare.TYPE.CONVEYOR_BELT)
					{
						// the turn is based on the direction entered and the exit direction
						MapSquare.DIRECTION dirEnter = Utilities.MoveDirection(plyrOn.Robot.Location.MapPosition, mp.Location.MapPosition);
						BoardLocation dest = new BoardLocation(mp.Location.MapPosition, plyrOn.Robot.Location.Direction);
						dest = dest.Rotate((int)sqDest.Conveyor.Direction - (int)dirEnter);

						Trace.WriteLine(string.Format("     conveyor - Player: {0} at {1} moved to {2}", plyrOn.PlayerEngine.Name,
						                              plyrOn.Robot.Location, dest));
						plyrOn.Robot.Location = dest;
						continue;
					}
					// we're going off the belt
					PlayerInternal plyrDestRobot =
						players.Where(pl => (pl.IsVisible) && (pl.Robot.Location.MapPosition == mp.Location.MapPosition)).FirstOrDefault();
					if (plyrDestRobot != null)
					{
						Trace.WriteLine(string.Format("     conveyor - Player: {0} at {1} cannot be moved", plyrOn.PlayerEngine.Name,
						                              plyrOn.Robot.Location));
						continue;
					}
					BoardLocation location = new BoardLocation(mp.Location.MapPosition, plyrOn.Robot.Location.Direction);
					Trace.WriteLine(string.Format("     conveyor - Player: {0} at {1} moved off the belt to {2}",
					                              plyrOn.PlayerEngine.Name,
												  plyrOn.Robot.Location, location));
					Trap.trap(location.Direction != plyrOn.Robot.Location.Direction);
					plyrOn.Robot.Location = location;
				}
			// have to validate after because we move on top of each other handling the conveyor belt
			ValidateData();

			List<PlayerInternal> plyrMoveGears = (from plyrOn in players
			                                      where plyrOn.IsVisible
			                                      let sq = MainMap.GetSquare(plyrOn.Robot.Location.MapPosition)
			                                      where
			                                      	((sq.Type == MapSquare.TYPE.ROTATE_CLOCKWISE) ||
			                                      	 (sq.Type == MapSquare.TYPE.ROTATE_COUNTERCLOCKWISE))
			                                      select plyrOn).ToList();
			foreach (PlayerInternal plyrOn in plyrMoveGears)
			{
				MapSquare sq = MainMap.GetSquare(plyrOn.Robot.Location.MapPosition);
				plyrOn.Robot.Location = plyrOn.Robot.Location.Rotate(sq.Type == MapSquare.TYPE.ROTATE_CLOCKWISE ? 1 : -1);
				Trace.WriteLine(string.Format("     gear - Player: {0} turned to {1}", plyrOn.PlayerEngine.Name,
				                              plyrOn.Robot.Location));
				ValidateData();
			}
		}

		// lasers fire.
		private void ExecuteFire()
		{
			Trace.WriteLine("Fire lasers:");
			// robots shoot
			bool fired = false;
			foreach (PlayerInternal playerOn in players)
			{
				// we do this instead of a LINQ where because one firing can kill a latter robot.
				if (playerOn.Mode != Player.MODE.ACTIVE)
					continue;

				if (FireLaser(playerOn.Robot.Location, false))
					fired = true;
			}
			// lasers shoot
			foreach (Laser laserOn in MainMap.Lasers)
				if (FireLaser(laserOn.Location, true))
					fired = true;
			if (fired)
				explosionPlayer.Play();
		}

		private struct LaserMove
		{
			internal readonly MapSquare.SIDE wallExit, wallEnter;
			internal readonly int xAdd, yAdd;

			public LaserMove(MapSquare.SIDE wallExit, MapSquare.SIDE wallEnter, int xAdd, int yAdd)
			{
				this.wallExit = wallExit;
				this.wallEnter = wallEnter;
				this.xAdd = xAdd;
				this.yAdd = yAdd;
			}
		}

		// DIRECTION order - NESW
		private static readonly LaserMove[] laserMove = {
		                                                	new LaserMove(MapSquare.SIDE.NORTH, MapSquare.SIDE.SOUTH, 0, -1),
		                                                	new LaserMove(MapSquare.SIDE.EAST, MapSquare.SIDE.WEST, 1, 0),
		                                                	new LaserMove(MapSquare.SIDE.SOUTH, MapSquare.SIDE.NORTH, 0, 1),
		                                                	new LaserMove(MapSquare.SIDE.WEST, MapSquare.SIDE.EAST, -1, 0)
		                                                };

		private bool FireLaser(BoardLocation location, bool isLaser)
		{

			LaserMove lm = laserMove[(int) location.Direction];
			int x = location.MapPosition.X;
			int y = location.MapPosition.Y;
			bool firstSquare = true;
			bool fired = false;

			while ((0 <= x) && (x < MainMap.Width) && (0 <= y) && (y < MainMap.Height))
			{
				MapSquare sq = MainMap.Squares[x][y];
				// can we move into this square?
				if ((!firstSquare) && ((sq.Walls & lm.wallEnter) != 0))
					break;

				// robot to hit? (can be us if isLaser is true)
				Point pt = new Point(x, y);
				PlayerInternal plyrTargetRobot =
					players.Where(pl => (pl.IsVisible) && (pl.Robot.Location.MapPosition == pt)).FirstOrDefault();
				if ((plyrTargetRobot != null) && (isLaser || (!firstSquare)))
				{
					// call this before IncreaseDamage because if it's killed it is moved to -1, -1
					Explosion expl = new Explosion(frameTicks, plyrTargetRobot.Robot.Location.MapPosition);
					sprites.Add(expl);
					laserBeams.Add(new LaserBeam(location.MapPosition, plyrTargetRobot.Robot.Location.MapPosition, isLaser));

					plyrTargetRobot.IncreaseDamage();
					fired = true;

					Trace.WriteLine(string.Format("     laser - Player: {0} at {1} hit by laser at {2}, damage {3}, lives {4}",
					                              plyrTargetRobot.PlayerEngine.Name, plyrTargetRobot.Robot.Location,
					                              location, plyrTargetRobot.Damage, plyrTargetRobot.Lives));
					if (plyrTargetRobot.Mode == Player.MODE.DEAD)
						mainWindow.StatusMessage(string.Format("Player {0} has been killed", plyrTargetRobot.PlayerEngine.Name));
					ValidateData();
					break;
				}

				// can we move out of this square?
				if ((sq.Walls & lm.wallExit) != 0)
					break;
				x += lm.xAdd;
				y += lm.yAdd;
				firstSquare = false;
			}
			return fired;
		}

		// end of each phase - on a flag or repair square.
		private void ExecuteFlagAndArchive()
		{

			// if on a flag, mark it; update archive square.
			foreach (PlayerInternal playerOn in players)
			{
				// we do this instead of a LINQ where because one firing can kill a latter robot.
				if (playerOn.Mode != Player.MODE.ACTIVE)
					continue;

				MapSquare sq = MainMap.GetSquare(playerOn.Robot.Location.MapPosition);
				if (sq.Type == MapSquare.TYPE.FLAG)
				{
					playerOn.TouchedFlag(playerOn.Robot.Location.MapPosition);
					continue;
				}

				if (sq.Type == MapSquare.TYPE.REPAIR)
				{
					playerOn.SetArchive(playerOn.Robot.Location.MapPosition);
					continue;
				}
			}
		}

		// end of turn - power up/down, un-destroy, repair
		private void ExecuteEndTurn()
		{

			foreach (PlayerInternal playerOn in players)
			{
				ValidateData();
				if (playerOn.Mode == Player.MODE.DEAD)
					continue;

				// power down - after all moves, was not killed
				if ((playerOn.Mode == Player.MODE.ACTIVE) && (playerOn.PowerMode == Player.POWER_MODE.ANNOUNCED))
				{
					playerOn.PowerDown();
					Trace.WriteLine(string.Format("power down - Player: {0}, damage {1}", playerOn.PlayerEngine.Name, playerOn.Damage));
				}
				else
					// power up - still alive
					if (playerOn.PowerMode == Player.POWER_MODE.DOWN)
					{
						playerOn.IncreaseDamage();
						playerOn.IncreaseDamage();
						playerOn.PowerUp();
						Trace.WriteLine(string.Format("power up - Player: {0}, damage {1}", playerOn.PlayerEngine.Name, playerOn.Damage));
						if (playerOn.Mode == Player.MODE.DEAD)
							continue;
					}

				// we bring the robot back in at the end of the turn if it was destroyed.
				if (playerOn.Mode == Player.MODE.DESTROYED)
				{
					// is the archive available?
					List<Point> reEnter = new List<Point>();
					PlayerInternal plyrDestRobot =
						players.Where(pl => (pl.IsVisible) && (pl.Robot.Location.MapPosition == playerOn.Archive)).FirstOrDefault();
					if (plyrDestRobot == null)
						reEnter.Add(playerOn.Archive);
					else
					{
						// ok, get all around the archive that aren't occupied.
						for (int x = playerOn.Archive.X - 1; x <= playerOn.Archive.X + 1; x++)
							for (int y = playerOn.Archive.Y - 1; y <= playerOn.Archive.Y + 1; y++)
								if ((0 <= x) && (x < MainMap.Width) && (0 <= y) && (y < MainMap.Height))
								{
									Point pt = new Point(x, y);
									plyrDestRobot = players.Where(pl => (pl.IsVisible) && (pl.Robot.Location.MapPosition == pt)).FirstOrDefault();
									if (plyrDestRobot == null)
										reEnter.Add(pt);
								}
					}
					playerOn.UnDestroy();
					GetRobotLocation(playerOn, playerOn.PlayerEngine, new GameMap(MainMap), reEnter);
					Trace.WriteLine(string.Format("un-destroy - Player: {0}, Lives {1}, Location {2}", playerOn.PlayerEngine.Name,
					                              playerOn.Lives, playerOn.Robot.Location));
				}

				MapSquare sq = MainMap.GetSquare(playerOn.Robot.Location.MapPosition);
				if (sq.Type == MapSquare.TYPE.REPAIR)
				{
					playerOn.Repair(playerOn.Robot.Location.MapPosition);
					Trace.WriteLine(string.Format("repair - Player: {0}, damage {1}", playerOn.PlayerEngine.Name, playerOn.Damage));
				}
				ValidateData();
			}
			cardOn = 0;
		}

		#endregion

		#region setup

		// setup for each player.
		private void SetupPlayers()
		{
			for (int ind = 0; ind < playerEngines.Length; ind++)
			{
				IPlayer playerEng = playerEngines[ind];
				PlayerInternal player = new PlayerInternal(Guid.NewGuid().ToString(), startSquare[ind],
				                                           new Robot(
				                                           	new BoardLocation(startSquare[ind], MapSquare.DIRECTION.NORTH),
				                                           	Guid.NewGuid().ToString(), robotBitmaps[ind]),
				                                           allFlags, playerEng, playerColors[ind]);

				GetRobotLocation(player, playerEng, new GameMap(MainMap), new List<Point> {startSquare[ind]});

				players.Add(player);
			}

			ValidateData();
		}

		// get where to place the robot - both start and un-destroy
		private void GetRobotLocation(PlayerInternal player, IPlayer playerEng, GameMap playerMap, List<Point> allowedPoints)
		{
			Exception setupException = null;
			PlayerSetup startPos = null;
			// This is sloppy as we leave this task to just run if it times out. However, there is no abort and a timeout is very unlikely
			// so this is the best of all the bad solutions.
			Task task = new Task(() =>
			                     	{
			                     		try
			                     		{
			                     			startPos = playerEng.Setup(playerMap, new Player(player),
			                     			                           new List<Point>(allowedPoints).ToArray());
			                     		}
			                     		catch (Exception ex)
			                     		{
			                     			setupException = ex;
			                     		}
			                     	});
			task.Start();
			bool completed = task.Wait(TURN_TIME_LIMIT);
			if (completed)
				task.Dispose();
			if (setupException != null)
				mainWindow.StatusMessage(string.Format("{0} Setup threw exception {1}", player.PlayerEngine.Name,
				                                       setupException.Message));
			else if (!completed)
				mainWindow.StatusMessage(string.Format("{0} Setup timed out", player.PlayerEngine.Name));
			if ((!completed) || (setupException != null) || (startPos == null))
				player.Robot.Location = new BoardLocation(allowedPoints[0], MapSquare.DIRECTION.NORTH);
			else
			{
				if (allowedPoints.Contains(startPos.Position))
					player.Robot.Location = new BoardLocation(startPos.Position, startPos.Direction);
				else
				{
					Trap.trap();
					mainWindow.StatusMessage(string.Format("{0} Setup returned a bad location", player.PlayerEngine.Name));
					player.Robot.Location = new BoardLocation(startSquare[0], MapSquare.DIRECTION.NORTH);
				}
			}
		}

		private static readonly Point[] allFlags = {new Point(5, 4), new Point(10, 11), new Point(1, 6)};

		private static readonly Point[] startSquare = {
		                                              	new Point(5, 15), new Point(6, 15), new Point(3, 14), new Point(8, 14),
		                                              	new Point(1, 13), new Point(10, 13), new Point(0, 12),
		                                              	new Point(11, 12)
		                                              };

		private static readonly Bitmap[] robotBitmaps = {
		                                                	Sprites.bot_0, Sprites.bot_1, Sprites.bot_2, Sprites.bot_3,
		                                                	Sprites.bot_4, Sprites.bot_5, Sprites.bot_6, Sprites.bot_7
		                                                };

		private static readonly Color[] playerColors = {
		                                               	Color.DarkKhaki, Color.DarkGreen, Color.Crimson, Color.Magenta,
		                                               	Color.DarkGray, Color.Orange, Color.CornflowerBlue, Color.DeepPink
		                                               };

		#endregion

		#region testing

#if DEBUG
		private void ValidateData()
		{

			bool error = false;
			foreach (PlayerInternal playerOn in players)
			{
				if ((playerOn.Damage < 0) || (10 < playerOn.Damage))
				{
					Trace.WriteLine(string.Format("ERROR - player {0} has a damage level of {1}", playerOn.PlayerEngine.Name,
					                              playerOn.Damage));
					error = true;
				}
				if ((playerOn.Lives < 0) || (3 < playerOn.Lives))
				{
					Trace.WriteLine(string.Format("ERROR - player {0} has a life level of {1}", playerOn.PlayerEngine.Name,
					                              playerOn.Lives));
					error = true;
				}
				if (playerOn.Score < 0)
				{
					Trace.WriteLine(string.Format("ERROR - player {0} has a score of {1}", playerOn.PlayerEngine.Name, playerOn.Score));
					error = true;
				}

				if ((turnOn > 1) && (playerOn.Cards.Length != (playerOn.Mode == Player.MODE.DEAD ? 0 : 5)))
				{
					Trace.WriteLine(string.Format("ERROR - player {0} in mode {1} has {2} cards", playerOn.PlayerEngine.Name,
					                              playerOn.Mode, playerOn.Cards.Length));
					error = true;
				}


				if ((playerOn.Mode == Player.MODE.ACTIVE) || (playerOn.Mode == Player.MODE.POWER_DOWN))
				{
					if ((playerOn.Robot.Location.MapPosition.X < 0) || (MainMap.Width <= playerOn.Robot.Location.MapPosition.X) ||
					    (playerOn.Robot.Location.MapPosition.Y < 0) || (MainMap.Height <= playerOn.Robot.Location.MapPosition.Y))
					{
						Trace.WriteLine(string.Format("ERROR - player {0} off board at {1}", playerOn.PlayerEngine.Name,
						                              playerOn.Robot.Location));
						error = true;
					}

					if (playerOn.Damage == 10)
					{
						Trace.WriteLine(string.Format("ERROR - player {0} in mode {1} has a damage level of 10",
						                              playerOn.PlayerEngine.Name, playerOn.Mode));
						error = true;
					}
					if (playerOn.Lives == 0)
					{
						Trace.WriteLine(string.Format("ERROR - player {0} in mode {1} has a life level of 0", playerOn.PlayerEngine.Name,
						                              playerOn.Mode));
						error = true;
					}

					// only 1 at this location on the map
					List<PlayerInternal> stackedPlayers =
						players.Where(pl => pl.IsVisible && (pl.Robot.Location.MapPosition == playerOn.Robot.Location.MapPosition)).ToList
							();
					if (stackedPlayers.Count > 1)
					{
						StringBuilder buf =
							new StringBuilder(string.Format("ERROR - map location {0} has players: ", playerOn.Robot.Location.MapPosition));
						foreach (PlayerInternal pl in stackedPlayers)
							buf.Append(string.Format("{0}, ", pl.PlayerEngine.Name));
						Trace.WriteLine(buf);
						error = true;
					}
				}

					// destroyed or dead
				else
				{
					if ((playerOn.Robot.Location.MapPosition.X != -1) || (playerOn.Robot.Location.MapPosition.Y != -1))
					{
						Trace.WriteLine(string.Format("ERROR - player {0} in mode {1} on board at {2}", playerOn.PlayerEngine.Name,
						                              playerOn.Mode,
						                              playerOn.Robot.Location));
						error = true;
					}

					if ((playerOn.Mode == Player.MODE.DESTROYED) && (playerOn.Lives == 0))
					{
						Trace.WriteLine(string.Format("ERROR - player {0} in mode {1} has a life level of 0", playerOn.PlayerEngine.Name,
						                              playerOn.Mode));
						error = true;
					}
					if ((playerOn.Mode == Player.MODE.DEAD) && (playerOn.Lives != 0))
					{
						Trace.WriteLine(string.Format("ERROR - player {0} in mode {1} has a life level of 0", playerOn.PlayerEngine.Name,
						                              playerOn.Mode));
						error = true;
					}
				}
			}
			Trap.trap(error);
		}
#else
		private void ValidateData()
		{
		}
#endif

		#endregion

	}
}
